/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2009 Philippe Durand <draekz@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Collections.Generic;
using System.Security.Cryptography;

using Anculus.Core;

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Galaxium.Protocol.Facebook
{
	public class RequestEntityImageArgs : EventArgs
	{
		private IEntity _entity;
		private IDisplayImage _oldImage;
		
		public IEntity Entity
		{
			get { return _entity; }
		}
		
		public IDisplayImage OldImage
		{
			get { return _oldImage; }
		}
		
		public RequestEntityImageArgs (IEntity entity, IDisplayImage image)
		{
			_entity = entity;
			_oldImage = image;
		}
	}
	
	public class BuddyListRequest : AbstractRequest
	{
		public event EventHandler<RequestEntityImageArgs> EntityImage;
		
		public BuddyListRequest(FBConnection connection) : base (connection)
		{
			
		}
		
		protected void OnEntityImage (IEntity entity, IDisplayImage image)
		{
			if (EntityImage != null)
				EntityImage (this, new RequestEntityImageArgs (entity, image));
		}
		
		public override void Begin ()
		{
			try
			{
				FBAccount account = (Session.Account as FBAccount);
				
				HttpWebRequest request = CreateHttpRequest ("POST", "/ajax/presence/update.php", false);
				
				Uri uri = request.RequestUri;
				
				// These cookies seem to always have the same value.
				request.CookieContainer.Add (uri, new Cookie("isfbe", "false"));
				request.CookieContainer.Add (uri, new Cookie("locale", "en_US"));
				request.CookieContainer.Add (uri, new Cookie("test_cookie", "1"));
				request.CookieContainer.Add (uri, new Cookie("login", "+"));
				request.CookieContainer.Add (uri, new Cookie("cur_max_lag", "3"));
				
				// This cookie sometimes is not available!
				if (SavedCookies["made_write_conn"] != null)
					request.CookieContainer.Add (uri, new Cookie ("made_write_conn", SavedCookies["made_write_conn"].Value));
				
				// These cookies we need the values from the login request.
				request.CookieContainer.Add (uri, new Cookie ("sid", SavedCookies["sid"].Value));
				request.CookieContainer.Add (uri, new Cookie ("datr", SavedCookies["datr"].Value));
				request.CookieContainer.Add (uri, new Cookie ("xs", SavedCookies["xs"].Value));
				request.CookieContainer.Add (uri, new Cookie ("c_user", SavedCookies["c_user"].Value));
				request.CookieContainer.Add (uri, new Cookie ("h_user", SavedCookies["h_user"].Value));
				request.CookieContainer.Add (uri, new Cookie ("login_x", SavedCookies["login_x"].Value));
				
				RequestState state = new RequestState ();
				state.Request = request;
				
				// Setup what we will send in the request to get the buddy list.
				state.PostString = string.Format ("user={0}&popped_out=true&force_render=true&buddy_list=1&", account.ProfileID);
				state.PostData = Encoding.UTF8.GetBytes (state.PostString);
				request.ContentLength = state.PostData.LongLength;
				
				request.BeginGetRequestStream (BeginGetBuddyList, state);
			}
			catch (WebException ex)
			{
				Log.Error (ex, "Unable to retrieve address book");
				
				OnFailed (new RequestErrorEventArgs ("Connection Failed!"));
			}
		}
		
		void BeginGetBuddyList (IAsyncResult ar)
		{
			if (ar != null)
			{
				RequestState state = (RequestState)ar.AsyncState;
				HttpWebRequest request = state.Request;
				state.Stream = request.EndGetRequestStream(ar);
				state.Stream.BeginWrite (state.PostData, 0, state.PostData.Length, BuddyListRequestSent, state);
			}
		}
		
		private void BuddyListRequestSent (IAsyncResult ar)
		{
			try
			{
				RequestState state = (RequestState)ar.AsyncState;

				state.Stream.Close();				
				
				state.Request.BeginGetResponse (BuddyListComplete, state.Request);
			}
			catch (Exception ex)
			{
				Log.Error (ex, "Unable to retrieve buddy list response");
				
				OnFailed (new RequestErrorEventArgs ("Connection Failed!"));
			}
		}
		
		void BuddyListComplete (IAsyncResult ar)
		{
			try
			{
				HttpWebRequest request = (HttpWebRequest)ar.AsyncState;
				HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(ar);
				Stream stream = response.GetResponseStream ();
				
				if (response.ContentLength > 0)
				{
					byte[] data = new byte[response.ContentLength];
					int done = 0, read;
					
					while ((read = stream.Read (data, done, (int)Math.Min (data.Length - done, 256))) > 0)
						done += read;
					
					string str = Encoding.UTF8.GetString (data);
					
					FacebookData fbData = (FacebookData)JavaScriptConvert.DeserializeObject(str.Substring (9), typeof(FacebookData));
					
					ParseBuddyList(fbData);
					
					OnCompleted ();
				}
				else
				{
					Log.Debug ("No presence response");
					
					OnFailed (new RequestErrorEventArgs ("Connection Failed!"));
				}
			}
			catch (Exception ex)
			{
				Log.Error (ex, "Unable to read presence result");
				
				OnFailed (new RequestErrorEventArgs ("Connection Failed!"));
			}
		}
		
		void ParseBuddyList (FacebookData fbData)
		{
			FBAccount account = (Session.Account as FBAccount);
			
			// Go through all the users that are in the chat system.
			foreach (string key in fbData.payload.buddy_list.userInfos.Keys)
			{
				// Create a contact from the information and put it in the list.
				UserInfoData nfo = (UserInfoData)fbData.payload.buddy_list.userInfos[key];
				
				// Check to see if this is us, the Account logged in.
				if (key.CompareTo (account.ProfileID.ToString()) == 0)
				{
					// We do not want to add him to the contact list.
					account.DisplayName = nfo.name;
					account.DisplayMessage = nfo.status;
					
					// Obtain the display image from the URL provided.
					WebClient accountPicReq = new WebClient ();
					accountPicReq.DownloadDataAsync (new Uri(nfo.thumbSrc), account);
					accountPicReq.DownloadDataCompleted += AccountPicDownloaded;
					continue;
				}
				
				FBContact contact = null;
				
				if (Session.ContactCollection.ContactExists (key))
				{
					// Contact already exists in the list.
					contact = (FBContact)Session.ContactCollection.GetContact(key);
				}
				else
				{
					// This is a new contact we need to add.
					contact = new FBContact (Session as FBSession, key, nfo.name);
					Session.ContactCollection.Add(contact);
					
					// There are no groups in facebook, add to base group.
					FBGroup group = (FBGroup)Session.GroupCollection.GetGroup ("0");
					group.Add(contact);
				}
				
				// Update the display message.
				contact.DisplayMessage = nfo.status;
				
				// Update the presence.
				foreach (string key2 in fbData.payload.buddy_list.nowAvailableList.Keys)
				{
					AvailabilityData avail = (AvailabilityData)fbData.payload.buddy_list.nowAvailableList[key2];
					if (avail.i)
						contact.Presence = FBPresence.Idle;
					else
						contact.Presence = FBPresence.Online;
				}
				
				// Obtaining the display image.
				WebClient contactPicReq = new WebClient ();
				contactPicReq.DownloadDataAsync (new Uri(nfo.thumbSrc), contact);
				contactPicReq.DownloadDataCompleted += AccountPicDownloaded;
			}
		}
		
		void AccountPicDownloaded (object obj, DownloadDataCompletedEventArgs args)
		{
			IEntity entity = (IEntity)args.UserState;
			IDisplayImage oldImage = entity.DisplayImage;
			entity.DisplayImage = new FBDisplayImage (args.Result);
			
			if (entity is IContact)
			{
				OnEntityImage (entity, oldImage);
			}
		}
	}
}
